use crate::capi::{
    iot_err_code_e, iot_err_code_e_RC_FAIL, iot_err_code_e_RC_INVALID_PARAMS, iot_err_code_e_RC_OK,
};
use crate::storage::UploadContext;
use alloc::boxed::Box;
use alloc::format;
use core::ffi::{c_char, c_uint, c_void};
use core::ptr;
use core::ptr::null_mut;
use embed_std::errorln;
use embed_std::util::cstr_to_str;

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct upload_context {
    pub priv_: *mut c_void,
    pub free: Option<unsafe extern "C" fn(ctx: *mut upload_context)>,
    pub set_timeout: Option<unsafe extern "C" fn(ctx: *mut upload_context, timeout: c_uint)>,
    pub upload: Option<
        unsafe extern "C" fn(
            ctx: *mut upload_context,
            key: *const c_char,
            filename: *const c_char,
            result: *mut upload_response_t,
        ) -> iot_err_code_e,
    >,
}

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct upload_response_t {
    pub url: [c_char; 256usize],
}

#[no_mangle]
pub unsafe extern "C" fn create_upload_context(
    api_schema: *const c_char,
    api_host: *const c_char,
    device_uid: *const c_char,
    secret: *const c_char,
) -> *mut upload_context {
    if api_schema.is_null() || api_host.is_null() || device_uid.is_null() {
        return null_mut();
    }
    let schema = cstr_to_str(api_schema);
    let host = cstr_to_str(api_host);
    let device_uid = cstr_to_str(device_uid);
    let secret = cstr_to_str(secret);
    let base_url = format!("{}://{}", schema, host);
    let ctx = Box::into_raw(UploadContext::new(base_url.as_str(), device_uid, secret));
    let c = Box::new(upload_context {
        priv_: ctx as *mut c_void,
        free: Some(free_upload_context),
        set_timeout: Some(set_upload_timeout),
        upload: Some(upload_file),
    });
    Box::into_raw(c)
}

unsafe extern "C" fn free_upload_context(ctx: *mut upload_context) {
    if ctx.is_null() {
        return;
    }
    let ctx1 = Box::from_raw(ctx);
    if !ctx1.priv_.is_null() {
        let c = Box::from_raw(ctx1.priv_ as *mut UploadContext);
        drop(c);
    }
    drop(ctx1);
}

unsafe extern "C" fn set_upload_timeout(ctx: *mut upload_context, timeout: c_uint) {
    if ctx.is_null() {
        return;
    }
    let r_ctx = Box::leak(Box::from_raw(ctx));
    if r_ctx.priv_.is_null() {
        return;
    }
    let c = Box::leak(Box::from_raw(r_ctx.priv_ as *mut UploadContext));
    c.set_timeout(timeout);
}

unsafe extern "C" fn upload_file(
    ctx: *mut upload_context,
    key: *const c_char,
    filename: *const c_char,
    result: *mut upload_response_t,
) -> iot_err_code_e {
    if ctx.is_null() || key.is_null() || filename.is_null() {
        return iot_err_code_e_RC_INVALID_PARAMS;
    }
    let r_ctx = Box::leak(Box::from_raw(ctx));
    if r_ctx.priv_.is_null() {
        return iot_err_code_e_RC_INVALID_PARAMS;
    }
    let c = Box::leak(Box::from_raw(r_ctx.priv_ as *mut UploadContext));
    let r_key = cstr_to_str(key);
    let r_filename = cstr_to_str(filename);
    match c.upload(r_key, r_filename) {
        Ok(r) => {
            if !result.is_null() {
                let res = &mut *result;
                let len = if r.url.len() > res.url.len() - 1 {
                    res.url.len() - 1
                } else {
                    r.url.len()
                };
                ptr::copy(r.url.as_ptr() as *const c_char, res.url.as_mut_ptr(), len);
                res.url[len] = 0;
            }
            iot_err_code_e_RC_OK
        }
        Err(err) => {
            errorln!("upload file {} failed: {}", r_filename, err);
            iot_err_code_e_RC_FAIL
        }
    }
}
